//
//  KVOViewController.m
//  DebugDemo
//
//  Created by XFB on 2018/11/13.
//  Copyright © 2018年 ZhouCheng. All rights reserved.
//  kVO的实现原理 Key Value Observing

#import "KVOViewController.h"
#import "Student.h"

@interface KVOViewController ()

@property (nonatomic, strong) Student *student;


@end

@implementation KVOViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    [self setupBasicSet];
    [self setupUI];
}

- (void)setupBasicSet
{
    self.view.backgroundColor = [UIColor whiteColor];
    self.navigationItem.title = self.titleString;
}

- (void)setupUI
{
    [self test1];
//    [self test2];
}

- (void)test1
{
    /// KVO作用： 能够监听某个对象属性值的改变
    _student = [[Student alloc] init];
//    _student.name = @"Tom";
    
    /**
     
     接收新值，默认为只接收新值
     接收旧值
     在注册时立即接收一次回调，在改变时也会发送通知
     改变之前发一次，改变之后发一次
    
     Context 参数 传入任意类型的对象，在接收消息回调的代码中可以接收到这个对象，是KVO中的一种传值方式
     
     student 并不会强引用self，所以需要注意观察者的生命周期，否则会导致观察者被释放带来的crash
     
     NSKeyValueChangeNewKeyNew：提供更改前的值【把更改之前的值提供给处理方法】
     NSKeyValueChangeOldKeyOld：提供更改后的值【把更改之后的值提供给处理方法】
     NSKeyValueObservingOptionInitial：观察最初的值（在注册观察服务时会调用一次触发方法）【初始化的值提供给处理方法，一旦注册，立马就会调用一次。通常它会带有新值，而不会带有旧值。】
     NSKeyValueObservingOptionPrior：分别在值修改前后触发方法（即一次修改有两次触发）【分2次调用。在值改变之前和值改变之后。】
     */

    /**
     给对象p添加一个观察者（监听器）
     注册观察者，实施监听；

     p: 被观察对象
     self: 观察者【监听器】
     forKeyPath里面带上属性值name。如UIView的frame、center等等 【属性名（需要监听哪个属性】
     options：有四个值 上面注释了 【option传递0代表不带任何参数进去】
     context：可以带一些参数。需要自己强转 上下文，可以为nil
     */
    
//    [student addObserver:self forKeyPath:NSStringFromSelector(@selector(name)) options:(NSKeyValueObservingOptionNew) context:nil];
//    [student addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
  
    [_student addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:nil];

    /**

     Printing description of self->_student->isa:
     Student
     
     Printing description of self->_student->isa:
     NSKVONotifying_Student
     
     
     使用 isa 搅拌技术（isa-swizzling）来实现的KVO
     观察者注册对象的一个属性 isa 观察对象的指针被修改，指着一个中间类而不是在真正的类。
     isa 指针的作用：每个对象都有 isa 指针，指向改对象的类，它告诉runtime系统这个对象的类是什么
     当某个类的对象第一次被观察时，系统就会在运行期间动态创建该类的一个派生类
     在这个派生类中重写基类中 任何被观察属性的 Setter 方法
     派生类在被重写的 setter 方法实现真正的通知机制，和之前的 键值观察那样
     基于设置属性会调用 Setter 方法，通过重写就获得KVO需要的通知机制
     
     优点:
     1.提供一个简单的方法来实现两个对象的同步
     2.能够提供观察的属性的新值和旧值
     3.每次属性值改变都是自动发送通知，不需要开发者手动实现
     4.用keypath来观察属性，因此也可以观察嵌套对象

     缺点:
     1.观察的属性必须使用字符串来定义，因此编译器不会出现警告和检查
     2.只能重写回调方法来后去通知，不能自定义selector，当观察多个对象的属性就要写if语句
     来判断当前的回调属于哪个对象的属性的回调
     
     代码改动不多
     1、处理发布投诉填写附加属性 对 电话号码、手机号、金额做了一些正则判断处理
     2、删除了无用的图片和文件，这个有点点多，可能会误删，调整文件目录，之前时间紧，写的比较乱
     3、部分按钮的颜色调整
     */
    
}

/**
 KVO的回调方法，在这里处理属性发生的变化

 @param keyPath 哪一个属性被改了 被修改的属性
 @param object 哪一个对象的属性被改了 被修改的属性所属对象
 @param change 改成什么样了 属性改变情况(新旧值)
 @param context 对应context context传过来的值
 */
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
    NSString *newValue = change[NSKeyValueChangeNewKey];
    NSString *oldValue = change[NSKeyValueChangeOldKey];
    NSLog(@"%@---%@", newValue, oldValue);
    NSLog(@"%@", change);
}

- (void)dealloc
{
    NSLog(@"%s", __func__);
    [self.student removeObserver:self forKeyPath:@"name"];
//    [self.student removeObserver:self forKeyPath:  @"cat"];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    static int a;
    /// 自动模式
//    self.student.name = [NSString stringWithFormat:@"%d", a ++];
    /// 手动模式
//    [self.student willChangeValueForKey:@"name"];
//    self.student.name = [NSString stringWithFormat:@"%d", a ++];
//    [self.student didChangeValueForKey:@"name"];
    /**
     KVO的触发和属性的值没有直接关系，只是和willChangeValue和didChangeValue是否调用有关系
     */
//    self.student.cat.age = a++;
//    self.student.cat.level = a++;
}



- (void)test2
{
    Student *s1 = [[Student alloc] init];
    Student *s2 = [ [Student alloc] init];
    
    SEL sel = @selector(setName:);
    IMP imp1 = [s1 methodForSelector:sel];
    IMP imp2 = [s2 methodForSelector:sel];
    
    NSLog(@"添加KVO之前：%p---%p", imp1, imp2);
    
    /// 添加KVO之前
    NSLog(@"添加之前%@---%@", object_getClass(s1), object_getClass(s2));
    
    [s1 addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
    
    NSLog(@"添加之后%@---%@", object_getClass(s1), object_getClass(s2));
    /**
     添加之前Student---Student
     添加之后NSKVONotifying_Student---Student
     */
    
    imp1 = [s1 methodForSelector:sel];
    imp2 = [s2 methodForSelector:sel];
    
    NSLog(@"添加KVO之后：%p---%p", imp1, imp2);
    
    /**
     添加KVO之前：0x108261010---0x108261010
     添加KVO之后：0x1085f990a---0x108261010
     */
    
    s1.name = @"123";
    s2.name = @"456";
    
}
@end

